<!--
 * @Author: xcl
 * @Date: 2022-04-20 18:05:17
 * @LastEditors: Please set LastEditors
 * @LastEditTime: 2022-10-17 15:48:34
 * @Description: babylon 3D模型渲染组件
-->
<template>
  <div class="babylon-com">
    <div id="loadingScreen">
      <img src="static/babylon/lib/img/3.png" alt="" />
      <p>{{ loadingText }}</p>
    </div>
    <canvas id="renderCanvas"></canvas>
    <div id="loadingScreenPercent">导入进度：0%</div>
    <div id="fps"></div>
    <div id="status"></div>
    <div id="notSupported" class="hidden">很抱歉，您的浏览器不支持WebGL...</div>
    <div class="menu">
      <div id="enableDebug">启用/禁用调试</div>
      <div id="fullscreen">全屏</div>
    </div>
  </div>
</template>

<script>
export default {
  name: "BabylonCom",
  components: {},
  data() {
    return {
      loadingText: "",

      clearColor: new BABYLON.Color3(0.6, 0.6, 0.6),
      ambientColor: new BABYLON.Color3(0.3, 0.3, 0.3),

      skyboxParameter: {
        type: 1,
        createBox: {
          name: "skyBox",
          size: 1000.0,
        },
        cubeTexture: {
          rootUrl: "static/babylon/textures/skybox/skybox",
        },
      },
      skyboxParameter2: {
        type: 2,
        name: "hdrSkyBox",
        createBox: {
          size: 1000.0,
        },
        cubeTexture: {
          createFromPrefilteredData: {
            url: "static/babylon/textures/environment.dds",
          },
        },
      },

      cameraType: "ArcRotateCamera",
      cameraParameter: {
        name: "Camera",
        alpha: 0,
        beta: 0,
        radius: 30,
        target: new BABYLON.Vector3(0, 30, 0),
        minZ: 1.0,
        position: new BABYLON.Vector3(20, 70, 120),
      },

      lightType: "DirectionalLight",
      lightParameter: {
        name: "dir01",
        direction: new BABYLON.Vector3(0, -0.5, -1.0),
        position: new BABYLON.Vector3(50, 250, 200),
        shadowOrthoScale: 2.0,
      },
    };
  },
  props: {},
  watch: {},
  created() {},
  mounted() {
    this.init();
  },
  beforeDestroy() {},
  destroyed() {
    this.disposeEvent();
  },
  methods: {
    /**
     * 初始化
     */
    init() {
      var self = this;
      var demo = {
        constructor: self.createScene,
        onload: function (scene) {
          console.log(scene);
          self.createMeshes(scene);
          // self.createGround(scene);
          self.importMesh(scene);
        },
      };
      self.loaderCustomJS(demo);
    },

    createScene(engine) {
      let self = this;

      const cP = this.cameraParameter;
      const cT = this.cameraType;
      const lP = this.lightParameter;
      const lT = this.lightType;
      const clearColor = this.clearColor;
      const ambientColor = this.ambientColor;
      const sP = this.skyboxParameter2;

      // Scene 建一个基本的Babylon场景对象
      var scene = new BABYLON.Scene(engine);

      // 背景颜色
      if (clearColor) {
        scene.clearColor = clearColor;
      }
      // 环境颜色
      if (ambientColor) {
        scene.ambientColor = ambientColor;
      }
      // Skybox
      if (
        sP &&
        Object.prototype.toString.call(sP) === "[object Object]" &&
        sP.type
      ) {
        if (sP.type == 1) {
          var skybox = BABYLON.Mesh.CreateBox(
            sP.name ? sP.name : "skyBox",
            sP.createBox && sP.createBox.size ? sP.createBox.size : 100.0,
            scene
          ); // 盒子
          var skyboxMaterial = new BABYLON.StandardMaterial(
            sP.name ? sP.name : "skyBox",
            scene
          ); // 材质
          skyboxMaterial.backFaceCulling = false; // 背面贴图显示
          skyboxMaterial.disableLighting = true; // 材质不受灯光影响
          skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture(
            sP.cubeTexture && sP.cubeTexture.rootUrl
              ? sP.cubeTexture.rootUrl
              : "",
            scene
          ); // 反射纹理
          skyboxMaterial.reflectionTexture.coordinatesMode =
            BABYLON.Texture.SKYBOX_MODE;
          skybox.material = skyboxMaterial; // 贴材质
          skybox.infiniteDistance = true; // 无线距离
          skybox.renderingGroupId = 0; // 让你的天空盒在其他东西后面渲染
        }
        if (sP.type == 2) {
          var hdrTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(
            sP.cubeTexture &&
              sP.cubeTexture.createFromPrefilteredData &&
              sP.cubeTexture.createFromPrefilteredData.url
              ? sP.cubeTexture.createFromPrefilteredData.url
              : "",
            scene
          ); // 场景天空盒的图片， 可以是.dds文件，也可以是图片
          var hdrSkybox = BABYLON.Mesh.CreateBox(
            sP.name ? sP.name : "hdrSkyBox",
            sP.createBox && sP.createBox.size ? sP.createBox.size : 100.0,
            scene
          ); // 盒子
          var hdrSkyboxMaterial = new BABYLON.PBRMaterial(
            sP.name ? sP.name : "hdrSkyBox",
            scene
          ); // 材质
          hdrSkyboxMaterial.backFaceCulling = false; // 背面贴图显示
          hdrSkyboxMaterial.disableLighting = true; // 材质不受灯光影响
          hdrSkyboxMaterial.reflectionTexture = hdrTexture.clone(); // 反射纹理
          hdrSkyboxMaterial.reflectionTexture.coordinatesMode =
            BABYLON.Texture.SKYBOX_MODE;
          hdrSkyboxMaterial.microSurface = 1.0; // 反射出的物体的细致程度
          hdrSkybox.material = hdrSkyboxMaterial; // 贴材质
          hdrSkybox.infiniteDistance = true; // 无线距离
          hdrSkybox.renderingGroupId = 0; // 让你的天空盒在其他东西后面渲染
        }
      }

      // Light 创建一个灯光
      var light;
      if (lT == "DirectionalLight") {
        // 参数有：name direction scene
        light = new BABYLON.DirectionalLight(
          lP && lP.name ? lP.name : "directionalLight",
          lP && lP.direction ? lP.direction : new BABYLON.Vector3(0, 0, 0),
          scene
        );
        if (lP && lP.position) {
          light.position = lP.position;
        }
        light.shadowOrthoScale =
          lP && lP.shadowOrthoScale ? lP.shadowOrthoScale : 0.1;
      }
      if (lT == "PointLight") {
        // 参数有：name position scene
        light = new BABYLON.PointLight(
          lP && lP.name ? lP.name : "pointLight",
          lP && lP.position ? lP.position : new BABYLON.Vector3(0, 0, 0),
          scene
        );
      }
      if (lT == "SpotLight") {
        // 参数有：name position direction angle exponent scene
        light = new BABYLON.SpotLight(
          lP && lP.name ? lP.name : "spotLight",
          lP && lP.position ? lP.position : new BABYLON.Vector3(0, 0, 0),
          lP && lP.direction ? lP.direction : new BABYLON.Vector3(0, 0, 0),
          lP && lP.angle ? lP.angle : 0,
          lP && lP.exponent ? lP.exponent : 0,
          scene
        );
      }
      if (lT == "HemisphericLight") {
        // 参数有：name direction scene
        light = new BABYLON.HemisphericLight(
          lP && lP.name ? lP.name : "hemisphericLight",
          lP && lP.direction ? lP.direction : new BABYLON.Vector3(0, 0, 0),
          scene
        );
        if (lP && lP.position) {
          light.position = lP.position;
        }
      }
      // 设置灯光光暗度、强度，范围：0~1
      if (lP && lP.intensity) {
        light.intensity = lP.intensity;
      }

      // Camera 创建一个相机
      var camera;
      if (cT == "ArcRotateCamera") {
        // 参数有：name alpha beta radius target scene setActiveOnSceneIfNoneActive
        camera = new BABYLON.ArcRotateCamera(
          cP && cP.name ? cP.name : "arcRotateCamera",
          cP && cP.alpha ? cP.alpha : 0,
          cP && cP.beta ? cP.beta : 0,
          cP && cP.radius ? cP.radius : 0,
          cP && cP.target ? cP.target : new BABYLON.Vector3(0, 0, 0),
          scene
        );
        camera.minZ = cP && cP.minZ ? cP.minZ : 0.0;
      }
      if (cT == "FreeCamera") {
        // 参数有：name position scene setActiveOnSceneIfNoneActive
        camera = new BABYLON.FreeCamera(
          cP && cP.name ? cP.name : "freeCamera",
          cP && cP.position ? cP.position : new BABYLON.Vector3(0, 0, 0),
          scene
        );
        camera.minZ = cP && cP.minZ ? cP.minZ : 0.0;
      }
      if (cT == "FollowCamera") {
        // 参数有：name position scene lockedTarget
        camera = new BABYLON.FollowCamera(
          cP && cP.name ? cP.name : "followCamera",
          cP && cP.position ? cP.position : new BABYLON.Vector3(0, 0, 0),
          scene
        );
        camera.minZ = cP && cP.minZ ? cP.minZ : 0.0;
      }
      if (cT == "UniversalCamera") {
        // 参数有：name position scene
        camera = new BABYLON.UniversalCamera(
          cP && cP.name ? cP.name : "universalCamera",
          cP && cP.position ? cP.position : new BABYLON.Vector3(0, 0, 0),
          scene
        );
        camera.minZ = cP && cP.minZ ? cP.minZ : 0.0;
      }
      if (cT == "AnaglyphUniversalCamera") {
        // 参数有：name position interaxialDistance scene
        camera = new BABYLON.AnaglyphUniversalCamera(
          cP && cP.name ? cP.name : "anaglyphUniversalCamera",
          cP && cP.position ? cP.position : new BABYLON.Vector3(0, 0, 0),
          cP && cP.interaxialDistance ? cP.interaxialDistance : 0,
          scene
        );
        camera.minZ = cP && cP.minZ ? cP.minZ : 0.0;
      }
      if (cT == "DeviceOrientationCamera") {
        // 参数有：name position scene
        camera = new BABYLON.DeviceOrientationCamera(
          cP && cP.name ? cP.name : "deviceOrientationCamera",
          cP && cP.position ? cP.position : new BABYLON.Vector3(0, 0, 0),
          scene
        );
        camera.minZ = cP && cP.minZ ? cP.minZ : 0.0;
      }
      if (cT == "VirtualJoysticksCamera") {
        // 参数有：name position scene
        camera = new BABYLON.VirtualJoysticksCamera(
          cP && cP.name ? cP.name : "virtualJoysticksCamera",
          cP && cP.position ? cP.position : new BABYLON.Vector3(0, 0, 0),
          scene
        );
        camera.minZ = cP && cP.minZ ? cP.minZ : 0.0;
      }
      if (cT == "VRDeviceOrientationFreeCamera") {
        // 参数有：name position scene compensateDistortion vrCameraMetrics
        camera = new BABYLON.VRDeviceOrientationFreeCamera(
          cP && cP.name ? cP.name : "vrDeviceOrientationFreeCamera",
          cP && cP.position ? cP.position : new BABYLON.Vector3(0, 0, 0),
          scene
        );
        camera.minZ = cP && cP.minZ ? cP.minZ : 0.0;
      }
      if (cT == "WebVRFreeCamera") {
        // 参数有：name position scene _webVROptions
        camera = new BABYLON.WebVRFreeCamera(
          cP && cP.name ? cP.name : "webVRFreeCamera",
          cP && cP.position ? cP.position : new BABYLON.Vector3(0, 0, 0),
          scene
        );
      }
      if (cT == "FlyCamera") {
        // 参数有：name position scene setActiveOnSceneIfNoneActive
        camera = new BABYLON.FlyCamera(
          cP && cP.name ? cP.name : "flyCamera",
          cP && cP.position ? cP.position : new BABYLON.Vector3(0, 0, 0),
          scene
        );
      }
      if (cP && cP.position) {
        camera.setPosition(cP.position);
      }

      return scene;
    },

    loaderCustomJS(demo) {
      this.disposeEvent();

      var self = this;
      // Canvas 获取画布元素
      var canvas = document.getElementById("renderCanvas");

      // UI
      var divFps = document.getElementById("fps");
      var enableDebug = document.getElementById("enableDebug");
      var status = document.getElementById("status");
      var fullscreen = document.getElementById("fullscreen");
      var notSupported = document.getElementById("notSupported");

      // Babylon
      var spector = new SPECTOR.Spector();
      spector.spyCanvases();
      var engine = new BABYLON.Engine(canvas, true, {
        preserveDrawingBuffer: true,
        stencil: true,
        disableWebGL2Support: false,
      });// 生成BABYLON三维引擎
      window.engine = engine;
      var scene;

      /** 创建自定义加载屏幕 */
      var loadingScreenDiv = window.document.getElementById("loadingScreen");
      function CustomLoadingScreen(text) {
        // creation
      }
      CustomLoadingScreen.prototype.displayLoadingUI = function () {
        // loading
        self.loadingText = "载入中...";
      };
      CustomLoadingScreen.prototype.hideLoadingUI = function () {
        // loaded
        loadingScreenDiv.style.display = "none";
      };
      var loadingScreen = new CustomLoadingScreen();
      // replace the default loading screen
      engine.loadingScreen = loadingScreen;

      var loadCustomScene = function (demoConstructor, then) {
        BABYLON.SceneLoader.ShowLoadingScreen = false;
        engine.displayLoadingUI(); // 开启载入动画

        setTimeout(function () {
          scene = demoConstructor(engine);
          window.scene = scene;
          if (scene.activeCamera) {
            // 将相机绑定到画布上面
            // 第二个值：为 false，babylon 会阻止 canvas 的默认事件；为 true，canvas 将会触发默认事件
            scene.activeCamera.attachControl(canvas, true);
          }

          scene.executeWhenReady(function () {
            canvas.style.opacity = 1;
            engine.hideLoadingUI(); // 关闭载入动画
            BABYLON.SceneLoader.ShowLoadingScreen = true;
            if (then) {
              then(scene);
            }

            for (var index = 0; index < scene.cameras.length; index++) {
              var camera = scene.cameras[index];
              var option = new Option();
              option.text = camera.name;
              option.value = camera;

              if (camera === scene.activeCamera) {
                option.selected = true;
              }
            }
            // 开启调试面板
            // spector.displayUI();
          });
        }, 15);

        return;
      };

      // 渲染循环
      var renderFunction = function () {
        // Fps
        divFps.innerHTML = engine.getFps().toFixed(2) + " fps";

        // 渲染场景，载入中...
        if (scene) {
          scene.render();
        }
      };
      // 注册一个渲染循环以重复渲染场景
      engine.runRenderLoop(renderFunction);

      // 自适应屏幕，调整大小
      window.addEventListener("resize", function () {
        engine.resize();
      });

      // 点击隐藏提示语（是否支持WebGL）
      notSupported.addEventListener("click", function () {
        document.getElementById("notSupported").className = "hidden";
      });

      // 点击切换开启/禁用调试
      enableDebug.addEventListener("click", function () {
        if (scene) {
          if (scene.debugLayer.isVisible()) {
            scene.debugLayer.hide();
          } else {
            scene.debugLayer.show();
          }
        }
      });

      // 点击全屏
      fullscreen.addEventListener("click", function () {
        if (engine) {
          engine.switchFullscreen(true);
        }
      });

      // 检查是否支持
      if (!BABYLON.Engine.isSupported()) {
        document.getElementById("notSupported").className = "";
      } else {
        loadCustomScene(demo.constructor, demo.onload);
      }
    },

    disposeEvent(){
      // 销毁Scene
      window.scene && window.scene.dispose();
      // 销毁Scene
      window.engine && window.engine.dispose();
    },

    createMeshes(scene) {
      var hdrTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(
        "static/babylon/textures/environment.dds",
        scene
      );

      // Create meshes
      var sphereGlass = BABYLON.Mesh.CreateSphere(
        "sphereGlass",
        48,
        30.0,
        scene
      );
      sphereGlass.translate(new BABYLON.Vector3(1, 0, 0), -60);

      var sphereMetal = BABYLON.Mesh.CreateSphere(
        "sphereMetal",
        48,
        30.0,
        scene
      );
      sphereMetal.translate(new BABYLON.Vector3(1, 0, 0), 60);

      var spherePlastic = BABYLON.Mesh.CreateSphere(
        "spherePlastic",
        48,
        30.0,
        scene
      );
      spherePlastic.translate(new BABYLON.Vector3(0, 0, 1), -60);

      var woodPlank = BABYLON.MeshBuilder.CreateBox(
        "plane",
        { width: 65, height: 1, depth: 65 },
        scene
      );

      // Create materials
      var glass = new BABYLON.PBRMaterial("glass", scene);
      glass.reflectionTexture = hdrTexture;
      glass.refractionTexture = hdrTexture;
      glass.linkRefractionWithTransparency = true;
      glass.indexOfRefraction = 0.52;
      glass.alpha = 0;
      glass.microSurface = 1;
      glass.reflectivityColor = new BABYLON.Color3(0.2, 0.2, 0.2);
      glass.albedoColor = new BABYLON.Color3(0.85, 0.85, 0.85);
      sphereGlass.material = glass;

      var metal = new BABYLON.PBRMaterial("metal", scene);
      metal.reflectionTexture = hdrTexture;
      metal.microSurface = 0.96;
      metal.reflectivityColor = new BABYLON.Color3(0.85, 0.85, 0.85);
      metal.albedoColor = new BABYLON.Color3(0.01, 0.01, 0.01);
      sphereMetal.material = metal;

      var plastic = new BABYLON.PBRMaterial("plastic", scene);
      plastic.reflectionTexture = hdrTexture;
      plastic.microSurface = 0.96;
      plastic.albedoColor = new BABYLON.Color3(0.206, 0.94, 1);
      plastic.reflectivityColor = new BABYLON.Color3(0.003, 0.003, 0.003);
      spherePlastic.material = plastic;

      var wood = new BABYLON.PBRMaterial("wood", scene);
      wood.reflectionTexture = hdrTexture;
      wood.environmentIntensity = 1;
      wood.specularIntensity = 0.3;

      wood.reflectivityTexture = new BABYLON.Texture(
        "static/babylon/PBR/reflectivity.png",
        scene
      );
      wood.useMicroSurfaceFromReflectivityMapAlpha = true;

      wood.albedoColor = BABYLON.Color3.White();
      wood.albedoTexture = new BABYLON.Texture(
        "static/babylon/PBR/albedo.png",
        scene
      );
      woodPlank.material = wood;
    },
    createGround(scene) {
      // Ground
      var ground = BABYLON.Mesh.CreateGround(
        "ground",
        1000,
        1000,
        1,
        scene,
        false
      );
      var groundMaterial = new BABYLON.StandardMaterial("ground", scene);
      groundMaterial.diffuseColor = new BABYLON.Color3(0.2, 0.2, 0.2);
      groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
      ground.material = groundMaterial;
      ground.receiveShadows = true;
    },

    importMesh(scene) {
      // Shadows
      var light = scene.getLightByName(this.lightParameter.name);
      var shadowGenerator = new BABYLON.ShadowGenerator(1024, light);
      shadowGenerator.useBlurVarianceShadowMap = false;

      // 导入模型 Dude
      BABYLON.SceneLoader.ImportMesh(
        "him",
        "static/babylon/Dude/",
        "Dude.babylon",
        scene,
        // onSuccess
        function (newMeshes2, particleSystems2, skeletons2) {
          var dude = newMeshes2[0];

          // 批量复制、克隆
          for (var index = 1; index < newMeshes2.length; index++) {
            shadowGenerator.getShadowMap().renderList.push(newMeshes2[index]);
          }
          for (var count = 0; count < 3; count++) {
            var offsetX = 200 * Math.random() - 100;
            var offsetZ = 200 * Math.random() - 100;
            for (index = 1; index < newMeshes2.length; index++) {
              var instance = newMeshes2[index].createInstance(
                "instance" + count
              );

              shadowGenerator.getShadowMap().renderList.push(instance);

              instance.parent = newMeshes2[index].parent;
              instance.position = newMeshes2[index].position.clone();

              if (!instance.parent.subMeshes) {
                instance.position.x += offsetX;
                instance.position.z -= offsetZ;
              }
            }
          }

          // 设置位置
          dude.rotation.y = Math.PI;
          dude.position = new BABYLON.Vector3(0, 0, -80);

          // 设置动画
          scene.beginAnimation(skeletons2[0], 0, 100, true, 1.0);
        },
        // onProgress
        function (evt) {
          var loadedPercent = 0;
          if (evt.lengthComputable) {
              loadedPercent = (evt.loaded * 100 / evt.total).toFixed();
          } else {
              var dlCount = evt.loaded / (1024 * 1024);
              loadedPercent = Math.floor(dlCount * 100.0) / 100.0;
          }
          document.getElementById("loadingScreenPercent").innerHTML = '导入进度：' + loadedPercent + '%';
          if(loadedPercent == 100){
            setTimeout(() => {
              document.getElementById("loadingScreenPercent").style.display = "none";
            }, 1000);
          }
        }
      );
    },
  },
};
</script>

<style scoped>
</style>

<style lang="less" scoped>
.babylon-com {
  width: 100%;
  height: 100%;
  position: relative;
  #notSupported {
    color: red;
    width: 100%;
    height: 60px;
    position: absolute;
    top: 50%;
    margin-top: -30px;
    background-color: #eeeeee;
    border: 1px solid #888888;
    text-align: center;
    padding-top: 10px;
    font-size: 30px;
    z-index: 3;
    cursor: default;
  }
  #renderCanvas {
    width: 100%;
    height: 100%;
    outline: none;
    touch-action: none;
  }
  @keyframes run {
    0% {
      transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg);
    }
    25% {
      transform: rotateX(0deg) rotateY(0deg) rotateZ(90deg);
    }
    50% {
      transform: rotateX(0deg) rotateY(0deg) rotateZ(180deg);
    }
    75% {
      transform: rotateX(0deg) rotateY(0deg) rotateZ(270deg);
    }
    100% {
      transform: rotateX(0deg) rotateY(0deg) rotateZ(360deg);
    }
  }
  #loadingScreen {
    position: absolute;
    width: 100%;
    height: 100%;
    color: white;
    font-size: 28px;
    text-align: center;
    background-color: rgba(0, 0, 0, 0.65);
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    img {
      display: block;
      margin-bottom: 20px;
      transform-style: preserve-3d;
      transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg);
      animation: run 3s linear infinite;
    }
  }
  #loadingScreenPercent{
    position: absolute;
    bottom: 0;
    right: 0;
    font-size: 20px;
    color: white;
    text-shadow: 2px 2px 0 black;
    background: #000;
    padding: 8px;
    display: inline-block;
  }
  #fps {
    position: absolute;
    right: 0px;
    top: 0px;
    font-size: 20px;
    color: white;
    text-shadow: 2px 2px 0 black;
    background: #000;
    padding: 8px;
  }
  #status {
    position: absolute;
    left: 20px;
    bottom: 20px;
    font-size: 14px;
    color: white;
    text-shadow: 2px 2px 0 black;
  }
  .hidden {
    display: none;
  }
  .warning {
    color: red;
  }

  a {
    color: black;
    text-decoration: none;
  }

  a:hover {
    text-decoration: underline;
  }

  a:visited {
    color: white;
  }

  .menu {
    position: fixed;
    bottom: 20px;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    left: 0;
    & > div {
      margin-right: 20px;
      height: 32px;
      line-height: 32px;
      border: 1px solid #333;
      background: #fff;
      border-radius: 4px;
      box-sizing: border-box;
      text-align: center;
      padding: 0 22px;
      cursor: pointer;
    }
  }
}
</style>
